package com.czkevin.cif.crafting.recipe;

import com.czkevin.cif.CustomItemFramework;
import com.czkevin.cif.crafting.RecipeMatch;
import com.czkevin.cif.crafting.ingredient.CraftingIngredient;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class CIFRecipe {

    private final String name;
    private final ItemStack output;

    private final Set<Character> usedChars = new HashSet<>();
    private final Map<Character, CraftingIngredient> ingredientMap = new HashMap<>();
    private final char[][] matrix;
    private final int width, height;
    private final int slot_count;

    public int requiredClicks = 1;

    public CIFRecipe(String name, ItemStack output, int requiredClicks, CharSequence... matrix) {
        this(name, output, matrix);
        if(requiredClicks < 0) throw new IllegalArgumentException("invalid click value");
        this.requiredClicks = requiredClicks;
    }

    public CIFRecipe(String name, ItemStack output, CharSequence... matrix) {
        if(matrix.length <= 0 || matrix.length > 5) throw new IllegalArgumentException("Invalid matrix height! ");
        width = matrix[0].length();
        height = matrix.length;
        slot_count = width * height;
        char[][] m = new char[matrix.length][];
        for(int y = 0; y < height; y++) {
            if(matrix[y].length() != width) throw new IllegalArgumentException("Invalid matrix width! ");
            m[y] = new char[width];
            for(int i = 0; i < width; i++) {
                m[y][i] = matrix[y].charAt(i);
                usedChars.add(m[y][i]);
            }
        }
        this.name = name;
        this.output = output;
        this.matrix = m;
    }

    public CIFRecipe(String name, ItemStack output, int requiredClicks, char[][] matrix) {
        this(name, output, matrix);
        if(requiredClicks < 0) throw new IllegalArgumentException("invalid click value");
        this.requiredClicks = requiredClicks;
    }

    public CIFRecipe(String name, ItemStack output, char[][] matrix) {
        if(matrix.length <= 0 || matrix.length > 5) throw new IllegalArgumentException("Invalid matrix height! ");
        width = matrix[0].length;
        height = matrix.length;
        slot_count = width * height;
        for(int y = 0; y < matrix.length; y++) {
            if(matrix[y].length != width) throw new IllegalArgumentException("Invalid matrix width! ");
            for(int i = 0; i < width; i++) usedChars.add(matrix[y][i]);
        }
        this.name = name;
        this.output = output;
        this.matrix = matrix;
    }

    public String getName() {
        return name;
    }

    public ItemStack getClonedOutput() {
        return output.clone();
    }

    public void setIngredient(char c, CraftingIngredient ingredient) {
        if(!usedChars.contains(c)) throw new IllegalArgumentException("Char not used! ");
        ingredientMap.put(c, ingredient);
    }

    public RecipeMatch matches(ItemStack[][] target) {
        if(target.length <= 0 || target.length > 5) throw new IllegalArgumentException("Invalid target! ");
        int targetHeight = target.length;
        int targetWidth = target[0].length;
        CustomItemFramework.debug(String.format("Matching recipe [%s]", name));
        for(int i = 1; i < width; i++) if(target[i].length != targetWidth) throw new IllegalArgumentException("Malformed target item matrix! ");
        if(targetHeight < height || targetWidth < width) {
            CustomItemFramework.debug(String.format("Target item matrix not hold the recipe matrix <%s>. ", name));
            return null;
        }
        for(int offsetY = 0; offsetY <= (targetHeight-height); offsetY ++) {
            for (int offsetX = 0; offsetX <= (targetWidth - width); offsetX++) {
                // ========================
                CustomItemFramework.debug(String.format("  -- Scanning at (%d, %d)", offsetX, offsetY));
                boolean look_next_offset = false;
                int matched_slots = 0;
                for (int ingredientY = 0; ingredientY < height; ingredientY++) {
                    for (int ingredientX = 0; ingredientX < width; ingredientX++) {
                        final int x = offsetX + ingredientX;
                        final int y = offsetY + ingredientY;
                        char ingredientChar = matrix[ingredientY][ingredientX];
                        if(ingredientChar == ' ') {
                            if(target[y][x] != null && target[y][x].getType() != Material.AIR) {
                                if(matched_slots > 0) return null;
                                look_next_offset = true;
                                break;
                            }
                            matched_slots ++;
                            continue;
                        }
                        CraftingIngredient ingredient = ingredientMap.get(ingredientChar);
                        if (ingredient == null) {
                            CustomItemFramework.debug(String.format("  -- Ingredient char [0x%02x] not found in recipe [%s]", (int) ingredientChar, name));
                            return null;
                        }
                        if(ingredient.matches(target[y][x])) {
                            matched_slots ++;
                        } else {
                            look_next_offset = true;
                            break;
                        }
                    }
                    if(look_next_offset) break;
                }
                if(matched_slots == slot_count) {
                    // check for other stuffs
                    for(int y = 0; y < target.length; y++) {
                        for (int x = 0; x < target[0].length; x++) {
                            if(y >= offsetY && y < offsetY+height && x >= offsetX && x < offsetX+width) continue; // within the recipe matrix
                            ItemStack targetItem = target[y][x];
                            if(targetItem != null && targetItem.getType() != Material.AIR) return null; // we have other stuffs in the crafting matrix
                        }
                    }
                    return new RecipeMatch(this, offsetX, offsetY);
                }
            }
        }
        return null;
    }

    public void removeLayer(ItemStack[][] items, RecipeMatch match) {
        for(int x = 0; x < width; x++) {
            for(int y = 0; y < height; y++) {
                if(matrix[y][x] == ' ') continue;
                int mx = match.x + x; int my = match.y + y;
                CraftingIngredient ingredient = ingredientMap.get(matrix[y][x]);
                int amount = ingredient.getAmount();
                if(items[my][mx] == null) continue;
                if(items[my][mx].getAmount() <= amount) {
                    items[my][mx] = null;
                } else {
                    items[my][mx].setAmount(items[my][mx].getAmount() - amount);
                }
            }
        }
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == null) return false;
        if(!CIFRecipe.class.isAssignableFrom(obj.getClass())) return false;
        return name.equals(((CIFRecipe) obj).name);
    }
}
